﻿Imports System.IO

Module modPatch
    Public Function Diff_Create(ByVal patchPath As String, ByVal patchOriginal As String, ByVal patchModified As String) _
        As Boolean?

        Dim _exe As String = Path.Combine(My.Settings.FolderPatch, "diff.exe")
        Dim _args As String = "-C 3 -iwbBa"

        If Not (File.Exists(patchOriginal)) Then Return Nothing
        If Not (File.Exists(patchModified)) Then Return Nothing

        If (Directory.Exists(patchOriginal) = True) Then

            _args = (_args & "r")

        End If

        Dim _cmd As New ProcessStartInfo

        _cmd.FileName = "cmd.exe"
        _cmd.Arguments = "/C """"" & _exe & """ " & _args & " """ & patchOriginal & """ """ & patchModified & """ > """ &
                         patchPath & """"""
        _cmd.Verb = "runas"
        _cmd.CreateNoWindow = True
        _cmd.UseShellExecute = False
        _cmd.WindowStyle = ProcessWindowStyle.Hidden

        Dim _process As Process = Process.Start(_cmd)

        _process.WaitForExit(10000)

        If (_process.HasExited = False) Then

            _process.Kill()
            Return False

        End If

        If File.Exists(patchPath) Then Return True

        Return False
    End Function

    Public Function Diff_Execute(ByVal filePatch As String, ByVal fileTarget As String,
                                 Optional ByVal optTestRun As Boolean = False) As en_resultPatch

        Dim _exe As String = Path.Combine(My.Settings.FolderPatch, "patch.exe")
        Dim _args As String = "-l -F 1 --verbose"
        Dim _return As en_resultPatch

        Dim _tmpOutput As String = String.Empty
        _tmpOutput = Path.Combine(frmMain._tmpDir,
                                  (DateTime.Now.ToString("MMddyy-Hmmss") & "-" & Math.Round(Rnd(999999), 4) & ".txt"))

        Dim _cmd As New ProcessStartInfo

        If (optTestRun = True) Then

            _args = (_args & " --dry-run")

        End If

        If Not (File.Exists(_exe)) Then Return en_resultPatch.ExeNotFound
        If Not (File.Exists(filePatch)) Then Return en_resultPatch.PatchNotFound
        If Not (File.Exists(fileTarget)) Then Return en_resultPatch.TargetNotFound

        _cmd.FileName = "cmd.exe"
        _cmd.Arguments = "/C " &
                         String_Quote(
                             String_Quote(_exe) & " " &
                             _args & " " &
                             String_Quote(fileTarget) & " < " &
                             String_Quote(filePatch) & " >> " &
                             String_Quote(_tmpOutput))
        _cmd.WindowStyle = ProcessWindowStyle.Hidden
        _cmd.CreateNoWindow = True
        _cmd.UseShellExecute = False
        _cmd.Verb = "runas"

        Error_Rerun:

        Dim _process As Process = Process.Start(_cmd)

        _process.WaitForExit(10000)

        If (_process.HasExited = False) Then

            If (_process.Responding = True) Then

                _process.CloseMainWindow()

                If _cmd.WindowStyle = ProcessWindowStyle.Normal Then Return en_resultPatch.ExeError

                MsgBox("There was an error. I am going to run the window unhidden for you to view the error.",
                       vbExclamation + vbOKOnly)
                _cmd.WindowStyle = ProcessWindowStyle.Normal

                GoTo Error_Rerun

            Else

                If _cmd.WindowStyle = ProcessWindowStyle.Normal Then Return en_resultPatch.ExeLockup

                _process.Kill()

                MsgBox("There was an error. I am going to run the window unhidden for you to view the error.",
                       vbExclamation + vbOKOnly)
                _cmd.WindowStyle = ProcessWindowStyle.Normal

                GoTo Error_Rerun

            End If

        End If

        _return = Diff_ReadOutput(_tmpOutput)

        'If optTestRun = True Then File.Delete(_tmpOutput)

        Return _return
    End Function

    Private Function Diff_ReadOutput(ByVal fileVerbose As String) As en_resultPatch

        Dim _read As streamreaderResult
        Dim _line As New List(Of String)
        Dim _tmp As String
        Dim _tmpLength As Integer

        Dim _hunkInt As Integer
        Dim _hunkResult As String
        Dim _hunkLine As String
        Dim _hunkOffset As Boolean

        Dim _hunks As New List(Of Tuple(Of Integer, String, Integer, Boolean))

        Dim _hasOffset As Boolean

        If Not (File.Exists(fileVerbose)) Then Return en_resultPatch.VerboseNotFound

        _read = File_ReadToList(fileVerbose, _line)

        If (_read = streamreaderResult.FileNotFound) Then Return en_resultPatch.VerboseNotFound

        For _index As Integer = 0 To (_line.Count - 1)

            _tmp = _line.Item(_index)

            If String_Substring(_tmp, 0, 6) = "Hunk #" Then

                _hunkInt = Integer_Between(_tmp, Chr(35), Chr(32))
                _hunkResult = String_Between(_tmp, _hunkInt & " ", " at")
                _hunkLine = String_Between(_tmp, _hunkResult & " at ", Chr(46))

                If _hunkLine.Contains("offset") Then

                    _hunkLine = Integer_Between(_tmp, _hunkResult & " at ", Chr(32))
                    _hunkOffset = True

                Else

                    _hunkOffset = False

                End If

                _hunks.Add(New Tuple(Of Integer, String, Integer, Boolean)(_hunkInt, _hunkResult, CInt(_hunkLine),
                                                                           _hunkOffset))

            ElseIf _tmp.Contains(" out of ") Then

                _tmpLength = 21 + (Len(_hunks.Count.ToString)*2)

                If (_hunks.Count > 1) Then _tmpLength = _tmpLength + 1

                _tmp = String_Substring(_tmp, 0, _tmpLength)

                If (_tmp = (_hunks.Count.ToString & " out of " & _hunks.Count.ToString & " hunk ignored") Or
                    _tmp = (_hunks.Count.ToString & " out of " & _hunks.Count.ToString & " hunks ignored")) Then

                    Return en_resultPatch.AlreadyPatched

                End If

            ElseIf _tmp.Contains("Hmm...  I can't seem to find a patch in there anywhere.") Then

                Return en_resultPatch.PatchNotFound

            ElseIf _tmp.Contains("done") Then

                For iH As Integer = 0 To (_hunks.Count - 1)

                    If (_hunks(iH).Item3 = 0) Or (_hunks(iH).Item2 = "FAILED") Then

                        Return en_resultPatch.Failure

                    End If

                    If (_hunks(iH).Item4 = True) Then _hasOffset = True

                Next

                If _hasOffset = True Then Return en_resultPatch.SuccessWithChanges

                Return en_resultPatch.Success

            End If

        Next

        Return en_resultPatch.Unknown
    End Function

    Public Function Patch_ReadOutput(ByVal outputPath As String, Optional ByVal hunkLines As List(Of String) = Nothing) _
        As en_resultPatch

        Dim _output As New List(Of String)
        Dim _verboseSummary As String = ""

        Dim _hunkNumber As String
        Dim _hunkResult As String
        Dim _hunkLine As String
        Dim _hunkOffset As Boolean
        Dim _tmp As String

        Dim _hunks As New List(Of Tuple(Of Integer, String, Integer, Boolean))

        Dim _hasOffset As Boolean
        Dim _hasApplied As Boolean
        Dim _hasError As Boolean
        Dim _missingPatch As Boolean

        If File_ReadToList(outputPath, _output) = streamreaderResult.FileNotFound Then

            Return en_resultPatch.VerboseNotFound

        End If

        For _index As Integer = 0 To (_output.Count - 1)

            Application.DoEvents()

            If String_Substring(_output.Item(_index), 0, 6) = "Hunk #" Then

                _hunkNumber = String_Between(_output.Item(_index), Chr(35), Chr(32))
                _hunkResult = String_Between(_output.Item(_index), _hunkNumber & " ", " at")
                _hunkLine = String_Between(_output.Item(_index), _hunkResult & " at ", Chr(46))

                If _hunkLine.Contains("offset") Then
                    _hunkLine = String_Between(_output.Item(_index), _hunkResult & " at ", Chr(32))
                    _hunkOffset = True
                Else
                    _hunkOffset = False
                End If

                _hunks.Add(New Tuple(Of Integer, String, Integer, Boolean)(CInt(_hunkNumber), _hunkResult,
                                                                           CInt(_hunkLine), _hunkOffset))

            ElseIf _output.Item(_index).Contains(" out of ") Then

                _verboseSummary = _output.Item(_index)

            ElseIf _output.Item(_index).Contains("Hmm...") Then

                _missingPatch = True

            ElseIf String_Substring(_output.Item(_index), 0, 4) = "done" Then

                For iH As Integer = 0 To (_hunks.Count - 1)

                    If (_hunks(iH).Item3 = 0) Or (_hunks(iH).Item2 = "FAILED") Then

                        _hasError = True

                    End If

                    If Not (hunkLines Is Nothing) Then

                        If (_hunks(iH).Item3 <> CInt(hunkLines(iH))) Then

                            _hasOffset = True

                        End If

                    End If

                    If (_hunks(iH).Item4 = True) Then
                        _hasOffset = True
                    End If

                Next

                If String_IsEmpty(_verboseSummary) = False Then

                    '((Len((_hunks.Count - 1).ToString) * 2) + Len(" out of  hunks ignored"))

                    _tmp = String_Substring(_verboseSummary, 0,
                                            (Len((_hunks.Count - 1).ToString)*2) + Len(" out of  hunks ignored"))

                    If _tmp = (_hunks.Count.ToString & " out of " & _hunks.Count.ToString & " hunks ignored") Then

                        _hasApplied = True

                    End If

                    _tmp = String_Substring(_verboseSummary, 0,
                                            (Len((_hunks.Count - 1).ToString)*2) + Len(" out of  hunk ignored"))

                    If _tmp = (_hunks.Count.ToString & " out of " & _hunks.Count.ToString & " hunk ignored") Then

                        _hasApplied = True

                    End If

                End If

                Exit For

            End If

        Next

        If _hasApplied = True Then

            Return en_resultPatch.AlreadyPatched

        ElseIf _hasError = True Then

            Return en_resultPatch.Failure

        ElseIf _hasOffset = True Then

            Return en_resultPatch.SuccessWithChanges

        ElseIf _missingPatch = True Then

            Return en_resultPatch.PatchNotFound

        Else

            Return en_resultPatch.Success

        End If
    End Function

    Public Function Patch_Execute(ByVal patchDiff As String, ByVal patchTarget As String,
                                  Optional ByVal hunkLines As List(Of String) = Nothing,
                                  Optional ByVal boolTestRun As Boolean = True,
                                  Optional ByVal boolDeleteOutput As Boolean = True) As en_resultPatch

        Dim patchExecutable As String = Path.Combine(My.Settings.FolderPatch, "patch.exe")
        Dim patchArgs As String

        Dim patchVerboseOutputPath As String = Path.GetDirectoryName(patchDiff)
        Dim patchVerboseOutput As String =
                (DateTime.Now.ToString("MMddyy-Hmmss") & "-" & Math.Round(Rnd(999), 0) & ".txt")
        Dim patchReturn As en_resultPatch

        Application.DoEvents()

        If File.Exists(patchExecutable) = False Then

            Return en_resultPatch.ExeNotFound

        End If

        patchTarget = File_FixPath(patchTarget)

        If File.Exists(patchTarget) = False Then

            Return en_resultPatch.TargetNotFound

        End If

        If boolTestRun = True Then

            patchVerboseOutput = Path.Combine(patchVerboseOutputPath, "patch-test-" & patchVerboseOutput)
            patchArgs = "-lN --dry-run --verbose"

        Else

            patchVerboseOutput = Path.Combine(patchVerboseOutputPath, "patch-" & patchVerboseOutput)
            patchArgs = "-lN --verbose"

        End If

        If File.Exists(patchVerboseOutput) Then

            'File.Delete(patchVerboseOutput)

        End If

        Dim procCmd As New ProcessStartInfo

        procCmd.FileName = "cmd.exe"
        procCmd.Arguments = "/C """"" & patchExecutable & """ " & patchArgs & " """ & patchTarget & """ < """ &
                            patchDiff & """ >> """ & patchVerboseOutput & """"""
        procCmd.WindowStyle = ProcessWindowStyle.Hidden
        procCmd.CreateNoWindow = True
        procCmd.UseShellExecute = False
        procCmd.Verb = "runas"

        Dim cmd As Process = Process.Start(procCmd)

        cmd.WaitForExit(10000)

        patchReturn = Patch_ReadOutput(patchVerboseOutput, hunkLines)

        If boolDeleteOutput = True Then

            If File.Exists(patchVerboseOutput) Then
                Application.DoEvents()

            End If
            'File.Delete(patchVerboseOutput)

        End If

        Return patchReturn
    End Function
End Module